//
import { TweenMax, Cubic } from "gsap";
import * as GoldenLayout from "golden-layout";
//
import { Map } from "common/types";
import { distance, Matrix, Point, Circle, Transform } from "common/geom";
import * as mvc from "common/mvc";
//
import * as dbtps from "../types";
import { BaseView } from "../base";
import Document from "../model/document";
import Stage from "../model/stage";
import { Pointer } from "../model/pointer";

const MAX_SCALE: number = 25.0;
const MIN_SCALE: number = 0.25;
const GRID_SCALE: number = 1.0; // 128 / 100

const helpPoint: Point = new Point();
/**
 * 缩放模式。
 */
const enum ScaleType {
    Normal, // 100%
    FitInSide, // 内切
    FitOutSide, // 外切
    None
}
/**
 * 网格基础贴图。 TODO
 */
const gridBT: PIXI.BaseTexture = new PIXI.BaseTexture(
    document.getElementById("stage_grid") as HTMLImageElement, PIXI.SCALE_MODES.NEAREST, 1.0
);
/**
 * 网格贴图。
 */
const gridT: PIXI.Texture = new PIXI.Texture(gridBT);
gridBT.mipmap = false;
gridBT.wrapMode = PIXI.WRAP_MODES.REPEAT;
/**
 * 文档视图。
 */
class StageView extends BaseView<Document, StageComponent> {
    /**
     * 为下一次缩放做标记。
     */
    private _scaleType: ScaleType = ScaleType.Normal;
    private readonly _pointerTransform: Transform = new Transform();
    private _stageModel: Stage;

    protected _initialize(): void {
        const dbtps = this.application.dbtps; // 初始化问题
        this._stageModel = this.model.stage;
        // 侦听数据通知。
        this._addNotification(dbtps.MNotificationType.StageVisibleChange, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.StageScaleChange, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.StageMove, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.StageResize, this._modelNotificationHandler);
        // 测试触控数据。
        this._addNotification(dbtps.MNotificationType.PointerDown, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.PointerMove, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.PointerUp, this._modelNotificationHandler);
        // 舞台滚轮事件。
        this.component.view.addEventListener("mousewheel", (event) => {
            if (event.wheelDelta > 0.0) {
                const scale = Math.max(Math.min(this._stageModel.scale * 1.3, MAX_SCALE), MIN_SCALE);
                if (this._stageModel.scale !== scale) {
                    helpPoint.x = event.offsetX - this._stageModel.x;
                    helpPoint.y = event.offsetY - this._stageModel.y;
                    helpPoint.x = this._stageModel.x + (helpPoint.x - helpPoint.x / this._stageModel.scale * scale);
                    helpPoint.y = this._stageModel.y + (helpPoint.y - helpPoint.y / this._stageModel.scale * scale);
                    this._scaleTo(scale, helpPoint);
                }
            }
            else {
                const scale = Math.max(Math.min(this._stageModel.scale * 0.7, MAX_SCALE), MIN_SCALE);
                if (this._stageModel.scale !== scale) {
                    helpPoint.x = this._stageModel.width - event.offsetX;
                    helpPoint.y = this._stageModel.height - event.offsetY;
                    helpPoint.x = this._stageModel.x + (helpPoint.x - this._stageModel.x) * 0.3;
                    helpPoint.y = this._stageModel.y + (helpPoint.y - this._stageModel.y) * 0.3;
                    this._scaleTo(scale, helpPoint);
                }
            }
        });
        // 兼容性多点触摸事件。
        this.component.view.ondblclick = (e) => {
            this._stageModel.pointer.removeAllPointer();
            this._scaleToType(this._scaleType + 1);
        };
        // 兼容性多点触摸事件。
        this.component.stage.on("pointerdown", this._stagePointerHandler, this);
        this.component.stage.on("pointermove", this._stagePointerHandler, this);
        this.component.stage.on("pointerup", this._stagePointerHandler, this);
        this.component.stage.on("pointerupoutside", this._stagePointerHandler, this);

        this.component.stage.on("pointerover", this._stagePointerHandler, this);
        this.component.stage.on("pointerout", this._stagePointerHandler, this);
        this.component.stage.on("pointerenter", this._stagePointerHandler, this);
        this.component.stage.on("pointerleave", this._stagePointerHandler, this);
        this.component.stage.on("pointercancel", this._stagePointerHandler, this);
        // 更新组件显示。
        this.component.updateVisible();
        this.component.updateScale();
        this.component.updatePosition();
        this.component.updateSize();
    }

    private _modelNotificationHandler(notification: dbtps.Notification): void {
        if (
            !(
                this._stageModel === notification.target ||
                this._stageModel.pointer === notification.target
            )
        ) { // 过滤无关的通知。
            return;
        }

        const dbtps = this.application.dbtps; // 初始化问题
        switch (notification.type) {
            case dbtps.MNotificationType.StageVisibleChange:
                this.component.updateVisible();
                break;

            case dbtps.MNotificationType.StageScaleChange:
                this.component.updateScale();
                break;

            case dbtps.MNotificationType.StageMove:
                this.component.updatePosition();
                break;

            case dbtps.MNotificationType.StageResize:
                this.component.updateSize();
                break;

            case dbtps.MNotificationType.PointerDown:
                this.application.currentDocument = this.model; // 点击舞台激活文档。
                TweenMax.killTweensOf(this._stageModel); // 清除舞台缓动。（缩放或移动）

                if (this._stageModel.pointer.pointerCount === 1) {
                    this._pointerTransform.x = this._stageModel.x;
                    this._pointerTransform.y = this._stageModel.y;
                }

                this.component.addPointer(notification.data as Pointer);
                break;

            case dbtps.MNotificationType.PointerMove:
                this._stageModel.x = this._pointerTransform.x + this._stageModel.pointer.offsetMatrix.tx;
                this._stageModel.y = this._pointerTransform.y + this._stageModel.pointer.offsetMatrix.ty;
                this.component.movePointer(notification.data as Pointer);
                break;

            case dbtps.MNotificationType.PointerUp:
                if (this._stageModel.pointer.pointerCount === 0) {
                    // 缓动停止。
                }

                this.component.removePointer(notification.data as Pointer);
                break;
        }
    }

    private _stagePointerHandler(event: PIXI.interaction.InteractionEvent): void {
        const global = event.data.global;
        const pointerEvent = event.data.originalEvent as PointerEvent;
        switch (event.type) {
            case "pointerdown":
                this._stageModel.pointer.addPointer(global.x, global.y, pointerEvent);
                break;

            case "pointermove":
                this._stageModel.pointer.updatePointer(global.x, global.y, pointerEvent);
                break;

            case "pointerup":
            case "pointerupoutside":
                this._stageModel.pointer.removePointer(pointerEvent);
                break;
        }
    }

    private _scaleTo(scale: number, point: Point, tweenDuration: number = 0.7): void {
        TweenMax.to(this._stageModel, tweenDuration, { scale: scale, ease: Cubic.easeOut } as any);
        if (point) {
            TweenMax.to(this._stageModel, tweenDuration, { x: point.x, y: point.y, ease: Cubic.easeOut } as any);
        }
    }

    private _scaleToType(type: ScaleType, tweenDuration: number = 0.7): void {
        this._scaleType = type;
        if (this._scaleType === ScaleType.None) {
            this._scaleType = ScaleType.Normal;
        }

        const width = this._stageModel.width;
        const height = this._stageModel.height;
        let scale = 1.0;
        if (this._stageModel.armature) {
            const aabb = this._stageModel.armature.aabb;
            const whA = width / height;
            const whB = aabb.width / aabb.height;

            switch (this._scaleType) {
                case ScaleType.Normal:
                    break;

                case ScaleType.FitInSide:
                    if (whA > whB) {
                        scale = height / (aabb.height + 100.0);
                    }
                    else {
                        scale = width / (aabb.width + 100.0);
                    }
                    break;

                case ScaleType.FitOutSide:
                    if (whA > whB) {
                        scale = width / (aabb.width + 100.0);
                    }
                    else {
                        scale = height / (aabb.height + 100.0);
                    }
                    break;
            }

            helpPoint.x = width * 0.5 - (aabb.x + aabb.width * 0.5) * scale;
            helpPoint.y = height * 0.5 - (aabb.y + aabb.height * 0.5) * scale;
        }
        else {
            helpPoint.x = width * 0.5;
            helpPoint.y = height * 0.5;
        }

        this._scaleTo(scale, helpPoint, tweenDuration);
    }
}
/**
 * 文档组件。
 */
export default class StageComponent extends PIXI.Application {
    /**
     * 数据源，只能访问。
     */
    public readonly model: Stage;
    /**
     * 顶层辅助。（骨骼图形，绘制图形，参考线等）
     */
    private readonly _containerTop: PIXI.Container = new PIXI.Container();
    /**
     * 骨架。（真实骨架）
     */
    private readonly _containerCenter: PIXI.Container = new PIXI.Container();
    /**
     * 底层辅助。（洋葱皮，其他背景等）
     */
    private readonly _containerBottom: PIXI.Container = new PIXI.Container();
    /**
     * 坐标线。
     */
    private readonly _lineH: PIXI.Graphics = new PIXI.Graphics();
    private readonly _lineV: PIXI.Graphics = new PIXI.Graphics();
    /**
     * 背景格。
     */
    private readonly _grid: PIXI.extras.TilingSprite = new PIXI.extras.TilingSprite(gridT);
    /**
     * 舞台容器。
     */
    private readonly _stage: PIXI.Container = new PIXI.Container();

    public constructor(model: Document) {
        super(128.0, 128.0, { backgroundColor: 0x666666 });

        this._lineH.lineStyle(1.0, 0x000000, 1.0);
        this._lineV.lineStyle(1.0, 0x000000, 1.0);
        this._lineH.moveTo(0.0, 0.0);
        this._lineV.moveTo(0.0, 0.0);
        this._lineH.lineTo(128.0, 0.0);
        this._lineV.lineTo(0.0, 128.0);
        this._grid.alpha = 0.1;
        this._grid.width = 128.0;
        this._grid.height = 128.0;

        this._containerTop.interactive = false;
        this._containerTop.interactiveChildren = true;
        this._containerCenter.interactive = false;
        this._containerCenter.interactiveChildren = true;
        this._containerBottom.interactive = false;
        this._containerBottom.interactiveChildren = true;
        this._lineH.interactive = false;
        this._lineV.interactive = false;
        this._grid.interactive = false;
        this._stage.interactive = true;
        this._stage.interactiveChildren = true;
        this.stage.interactive = true;
        this.stage.interactiveChildren = true;

        this._stage.addChild(this._grid);
        this._stage.addChild(this._lineH);
        this._stage.addChild(this._lineV);
        this._stage.addChild(this._containerBottom);
        this._stage.addChild(this._containerCenter);
        this._stage.addChild(this._containerTop);
        this.stage.addChild(this._stage);
        // 
        this.model = model.stage;
        // 注册视图。
        const view = new StageView();
        view.initialize(model, this);
    }
    /**
     * 更新网格尺寸。
     */
    private _updateGridSize(): void {
        const scale = this.model.scale * GRID_SCALE * 1.0;
        const imageWidth = gridT.width;
        const imageHeight = gridT.height;
        this._grid.width = this.model.width / scale + imageWidth;
        this._grid.height = this.model.height / scale + imageHeight;
    }
    /**
     * 更新舞台可见。
     */
    public updateVisible(): void {
        this.stage.visible = this.model.visible;
    }
    /**
     * 更新舞台缩放。
     */
    public updateScale(): void {
        const scale = this.model.scale;
        this._containerBottom.scale.set(scale, scale);
        this._grid.scale.set(scale, scale);
        this._updateGridSize();
    }
    /**
     * 更新舞台位置。
     */
    public updatePosition(): void {
        const scale = this.model.scale * GRID_SCALE * 1.0;
        const x = this.model.x;
        const y = this.model.y;
        const imageWidth = gridT.width * scale;
        const imageHeight = gridT.height * scale;
        this._lineH.x = -x;
        this._lineV.y = -y;
        this._grid.position.set(
            -Math.ceil(x / imageWidth) * imageWidth,
            -Math.ceil(y / imageHeight) * imageHeight
        );
        this._stage.position.set(x, y);
    }
    /**
     * 更新舞台尺寸。
     */
    public updateSize(): void {
        this._lineH.width = this.model.width;
        this._lineV.height = this.model.height;
        this._updateGridSize();
        this.renderer.resize(this.model.width, this.model.height);
    }
    /**
     * 测试触控数据。
     */
    private readonly _pointers: Map<PIXI.Graphics> = {};
    private readonly _pointerPool: PIXI.Graphics[] = [];
    private _getPointer(): PIXI.Graphics {
        if (this._pointerPool.length > 0) {
            const pointer = this._pointerPool.pop();
            if (pointer !== undefined) {
                return pointer;
            }
        }

        return new PIXI.Graphics();
    }
    public addPointer(data: Pointer): void {
        const pointer = this._pointers[data.event.pointerId] = this._getPointer();
        pointer.clear();
        pointer.lineStyle(4, 0xFF00FF, data.event.pressure * 0.5);
        pointer.drawEllipse(0.0, 0.0, Math.max(data.event.width * 1.5, 10), Math.max(data.event.height * 1.5, 10));
        this.stage.addChild(pointer);

        this.movePointer(data);
    }
    public movePointer(data: Pointer): void {
        const pointer = this._pointers[data.event.pointerId];
        pointer.position.set(data.x, data.y);
    }
    public removePointer(data: Pointer): void {
        const pointer = this._pointers[data.event.pointerId];
        delete this._pointers[data.event.pointerId];
        this.stage.removeChild(pointer);
        this._pointerPool.push(pointer);
    }
}